package liming.tool.file;

import java.io.*;
import java.nio.charset.Charset;
import java.nio.charset.MalformedInputException;
import java.nio.charset.UnmappableCharacterException;
import java.nio.file.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

/**
 * 文件读写工具类，提供多种编码格式支持和高效的大文件操作
 * 功能包括：
 * - 文本文件读写（支持多种编码）
 * - 二进制文件读写
 * - 大文件复制（基于NIO的高效实现）
 * - 异常堆栈信息处理
 * - 编码格式管理
 */
public class FileRW {
    //===================== 跨平台增强部分 ======================//
    enum OSType {WINDOWS, LINUX, MAC, OTHER}

    /**
     * 获取操作系统类型
     */
    private static OSType getOS() {
        String osName = System.getProperty("os.name").toLowerCase();
        if (osName.contains("win")) return OSType.WINDOWS;
        if (osName.contains("nix") || osName.contains("nux")) return OSType.LINUX;
        if (osName.contains("mac")) return OSType.MAC;
        return OSType.OTHER;
    }

    /**
     * 智能路径格式化
     */
    public static String formatPath(String rawPath) {
        String separator = FileSystems.getDefault().getSeparator();
        return rawPath.replace("/", separator)
                .replace("\\", separator);
    }

    /**
     * 动态默认编码检测
     */
    private static FileEncoding detectSystemEncoding() {
        return (getOS() == OSType.WINDOWS) ? FileEncoding.GBK : FileEncoding.UTF8;
    }
    //===================== 编码管理 ========================//
    /**
     * 默认编码格式（线程安全的初始化）
     */
    private static volatile FileEncoding READING_ENCODING = FileEncoding.AUTO;
    private static volatile FileEncoding WRITING_ENCODING = FileEncoding.AUTO;
    /**
     * 控制台编码（用于输入输出信息）
     */
    public static final Charset CONSOLE_ENCODING = FileEncoding.getConsoleEncoding();

    //===================== 编码格式枚举 ========================//
    public enum FileEncoding {
        AUTO("AUTO"),
        UTF8("UTF-8"),
        GBK("GBK");

        private final String value;

        FileEncoding(String value) {
            this.value = value;
        }

        public Charset getValue() {
            if(this==AUTO)
                return detectSystemEncoding().getValue();
            return Charset.forName(value);
        }

        public static FileEncoding fromName(String name) {
            for (FileEncoding encoding : values()) {
                if (encoding.value.equalsIgnoreCase(name)) {
                    return encoding;
                }
            }
            return AUTO;
        }

        // 新增编码探测方法
        public static Charset probeEncoding(Path path) throws IOException {
            List<Charset> candidates = new ArrayList<>();
            for (FileEncoding encoding : FileEncoding.values())
                if (encoding != FileEncoding.AUTO)
                    candidates.add(encoding.getValue());
            byte[] sampleBytes = Files.readAllBytes(path); // 仅读取前1024字节
            int sampleSize = Math.min(sampleBytes.length, 1024);
            for (Charset charset : candidates) {
                try {
                    new String(sampleBytes, 0, sampleSize, charset);
                    // 二次验证
                    try (BufferedReader reader = Files.newBufferedReader(path, charset)) {
                        reader.readLine(); // 仅验证首行
                    }
                    return charset;
                } catch (MalformedInputException | UnmappableCharacterException e) {
                    // 继续尝试下一个编码
                }
            }
            throw new IOException("无法自动识别文件编码: " + path);
        }

        public static Charset getConsoleEncoding() {
            Console console = System.console();
            if (console == null)
                switch (getOS()) {
                    case LINUX:
                    case MAC: return FileEncoding.UTF8.getValue();
                    default : return FileEncoding.GBK.getValue();
                }
            try {
                return Charset.forName(System.getProperty("console.encoding", "UTF-8"));
            } catch (Exception e) {
                switch (getOS()) {
                    case LINUX:
                    case MAC: return FileEncoding.UTF8.getValue();
                    default : return FileEncoding.GBK.getValue();
                }
            }
        }
    }

    //===================== 编码配置方法 ========================//

    /**
     * 设置全局默认编码格式
     *
     * @param encoding 新的编码格式
     * @return 设置后的编码名称
     */
    public static synchronized String setDefaultWritingEncoding(FileEncoding encoding) {
        Objects.requireNonNull(encoding, "编码格式不能为空");
        WRITING_ENCODING = encoding;
        return encoding.toString();
    }

    public static synchronized String setDefaultReadingEncoding(FileEncoding encoding) {
        Objects.requireNonNull(encoding, "编码格式不能为空");
        READING_ENCODING =  encoding;
        return encoding.toString();
    }

    /**
     * 获取当前默认编码格式名称
     */

    public static FileEncoding getReadingEncoding() {
        return READING_ENCODING;
    }

    public static FileEncoding getWritingEncoding() {
        return WRITING_ENCODING;
    }
    //===================== 核心文件操作方法 ========================//

    /**
     * 读取文本文件内容（使用默认编码）
     *
     * @param file 目标文件
     * @return 文件内容字符串
     * @throws IOException 读取失败时抛出
     */
    public static ReadString readTextFile(File file) throws IOException {
        return readTextFile(file, READING_ENCODING);
    }

    /**
     * 读取文本文件内容（指定编码）
     *
     * @param file         目标文件
     * @param fileEncoding 指定编码格式
     * @return 文件内容字符串
     * @throws IOException 读取失败时抛出
     */
    public static ReadString readTextFile(File file, FileEncoding fileEncoding) throws IOException {
        if(!file.exists())
            return new ReadString(file, fileEncoding, fileEncoding.getValue(), "file:"+file+"不存在");
        Charset actualCharset = fileEncoding == FileEncoding.AUTO ?
                FileEncoding.probeEncoding(file.toPath()) :
                fileEncoding.getValue();
        try (BufferedReader reader = Files.newBufferedReader(file.toPath(), actualCharset)) {
            StringBuilder content = new StringBuilder();
            String line;
            while ((line = reader.readLine()) != null) {
                content.append(line).append(System.lineSeparator());
            }
            if (content.length() > 0) {
                content.setLength(content.length() - System.lineSeparator().length());
            }
            return new ReadString(file, fileEncoding, actualCharset, content.toString());
        }
    }

    /**
     * 写入文本文件（使用默认编码）
     *
     * @param file    目标文件
     * @param content 要写入的内容
     * @param append  是否追加模式
     * @throws IOException 写入失败时抛出
     */
    public static void writeTextFile(File file, String content, boolean append) throws IOException {
        writeTextFile(file, content, append, WRITING_ENCODING);
    }

    /**
     * 写入文本文件（指定编码）
     *
     * @param file     目标文件
     * @param content  要写入的内容
     * @param append   是否追加模式
     * @param encoding 指定编码格式
     * @throws IOException 写入失败时抛出
     */
    public static void writeTextFile(File file, String content, boolean append, FileEncoding encoding)
            throws IOException {
        // 确保父目录存在
        Path path = Paths.get(formatPath(file.getAbsolutePath()));
        if (!Files.exists(path.getParent())) {
            Files.createDirectories(path.getParent());
        }
        Charset charset = (encoding == FileEncoding.AUTO) ?
                detectSystemEncoding().getValue() :
                encoding.getValue();
        try (OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(file, append), charset)) {
            writer.append(content);
            writer.flush();
        }
    }

    //===================== 新增跨平台方法 ========================//

    /**
     * 获取系统推荐换行符
     */
    public static String getLineSeparator() {
        return getOS() == OSType.WINDOWS ? "\r\n" : "\n";
    }

    /**
     * 平台兼容的文件复制
     */
    public static void crossPlatformCopy(File source, File dest) throws IOException {
        Path src = Paths.get(formatPath(source.getPath()));
        Path dst = Paths.get(formatPath(dest.getPath()));

        Files.copy(src, dst, StandardCopyOption.REPLACE_EXISTING,
                StandardCopyOption.COPY_ATTRIBUTES);
    }


    //===================== 二进制文件操作 ========================//

    /**
     * 读取二进制文件内容
     *
     * @param file 目标文件
     * @return 文件字节数据
     * @throws IOException 读取失败时抛出
     */
    public static byte[] readBinaryFile(File file) throws IOException {
        if (file.length() > Integer.MAX_VALUE) {
            throw new IllegalArgumentException("文件大小超过最大限制（2GB）");
        }
        try (InputStream is = Files.newInputStream(file.toPath())) {
            byte[] data = new byte[(int) file.length()];
            int bytesRead = is.read(data);
            if (bytesRead != data.length) {
                throw new IOException("文件读取不完整，预期：" + data.length + " 实际：" + bytesRead);
            }
            return data;
        }
    }

    /**
     * 写入二进制文件
     *
     * @param file   目标文件
     * @param data   要写入的字节数据
     * @param append 是否追加模式
     * @throws IOException 写入失败时抛出
     */
    public static void writeBinaryFile(File file, byte[] data, boolean append) throws IOException {
        // 确保父目录存在
        if (file.getParentFile() != null && !file.getParentFile().exists()) {
            if (!file.getParentFile().mkdirs()) {
                throw new IOException("无法创建父目录: " + file.getParent());
            }
        }
        try (FileOutputStream fos = new FileOutputStream(file, append)) {
            fos.write(data);
        }
    }

    //===================== 大文件操作 ========================//

    /**
     * 高效复制大文件（使用NIO通道传输）
     *
     * @param source 源文件
     * @param target 目标文件
     * @throws IOException 复制失败时抛出
     */
    public static void copyLargeFile(File source, File target) throws IOException {
        // 确保目标文件的父目录存在
        if (target.getParentFile() != null && !target.getParentFile().exists()) {
            if (!target.getParentFile().mkdirs()) {
                throw new IOException("无法创建目标文件的父目录: " + target.getParent());
            }
        }

        Path sourcePath = source.toPath();
        Path targetPath = target.toPath();
        Files.copy(sourcePath, targetPath, StandardCopyOption.REPLACE_EXISTING);
    }

    //===================== 异常处理工具 ========================//

    /**
     * 获取异常堆栈字符串
     *
     * @param throwable 异常对象
     * @return 格式化的堆栈跟踪信息
     */
    public static String getStackTrace(Throwable throwable) {
        if(throwable == null)
            return "无异常";
        try (StringWriter sw = new StringWriter();
             PrintWriter pw = new PrintWriter(sw)) {
            throwable.printStackTrace(pw);
            pw.flush();
            return sw.toString();
        } catch (IOException e) {
            // 理论上不会发生
            return "获取堆栈信息时发生异常: " + e.getMessage();
        }
    }

    public static class ReadString{
        private final File file;
        private final FileEncoding appointEncoding;
        private final Charset encoding;
        private final String content;
        ReadString(File file,FileEncoding appointEncoding, Charset encoding, String content){
            this.file = file;
            this.appointEncoding = appointEncoding;
            this.encoding = encoding;
            this.content = content;
        }

        public File getFile() {
            return file;
        }

        public FileEncoding getAppointEncoding() {
            return appointEncoding;
        }

        public Charset getEncoding() {
            return encoding;
        }

        public String getContent(){
            return content;
        }

        public int length(){
            return content.length();
        }

        @Override
        public String toString() {
            return getContent();
        }

        public String info(){
            return "ReadString{" +
                    "file=" + file +
                    ", 指定编码=" + appointEncoding +
                    ", 识别编码=" + encoding +
                    ", content='" + content + '\'';
        }
    }
}